package biggestloser.servlet;

import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;
import java.util.logging.Logger;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import biggestloser.util.DatastoreUtil;

import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.*;

/**
 * This class handles all POST events from admin.jsp. When it finishes a given
 * POST command it simply returns the admin.jsp as its response, potentially
 * with an error/success message.
 */
public class Admin extends HttpServlet
{
	private static final Logger log = Logger.getLogger(Admin.class.getName());
	
	private static final long serialVersionUID = -6573664489245640981L;

	/**
	 * This should never get called. All GET requests go directly to admin.jsp.
	 */
	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
    {        
        // Call the real doGet() with no message
        doGet(req, resp, null);
    }
	
	/**
	 * Returns the admin.jsp page along with any msg returned from doPost().
	 */
	private void doGet(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException
	{
		// Simply return the admin.jsp file. Any error or success messages from
		// doPost() get put into a query parameter.
		resp.sendRedirect("/admin?msg=" + msg);
	}
	
	/**
	 * Handle POST commands from admin.jsp. Return an optional msg argument 
	 * to indicate result of the command.
	 */
	public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException
	{
		// We'll use this to send back any error/success messages from the work below. 
		// This gets passed to doGet().
		String msg = "";
		
		// Reference to data store
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		
		// Add a user
		if(req.getParameter("name") != null && req.getParameter("email") != null)
		{
			// Verify user and email do not exist
			Query userQ = new Query("User");
			userQ.setFilter(
					new CompositeFilter(
							CompositeFilterOperator.OR,
							Arrays.<Filter>asList(
									new FilterPredicate("name",  FilterOperator.EQUAL, req.getParameter("name")),
									new FilterPredicate("email", FilterOperator.EQUAL, req.getParameter("email"))
							)
					)
			);
			
			PreparedQuery userPq = datastore.prepare(userQ);
			if(userPq.countEntities(FetchOptions.Builder.withDefaults()) > 0)
			{
				msg = "Error: User or email already exists!";
			}
			else
			{
				// Now we can add user to datastore
				Entity user = new Entity("User");
				user.setProperty("name",   req.getParameter("name"));
				user.setProperty("email",  req.getParameter("email"));
				user.setProperty("status", "enabled");
				
				for(int i = 1; i <= 12; i++)
				{
					user.setProperty("week" + i, 0.0);
				}
				datastore.put(user);
				msg = "Added user " + req.getParameter("name") + " [" + req.getParameter("email") + "]";
			}
		}
		
		// Send message to all users
		else if(req.getParameter("subject") != null && req.getParameter("message") != null)
		{
			// Default message to success; any errors will override this later
			msg = "Successfully sent message!";
			
			String subj = req.getParameter("subject");
			String message  = req.getParameter("message");
			
			// Load all users
			 Query q = new Query("User");
		     PreparedQuery pq = datastore.prepare(q);
		        
		     for (Entity result : pq.asIterable())
			 {
		    	 String name  = (String) result.getProperty("name");
		    	 String email = (String) result.getProperty("email");
		    	 
		    	 // Send an email
		    	 Properties props = new Properties();
		         Session session = Session.getDefaultInstance(props, null);
		         
		         try
		         {
		             Message emailMsg = new MimeMessage(session);
		             emailMsg.setFrom(new InternetAddress("admin@lynxbiggestloser.appspotmail.com", "Lynx Biggest Loser Admin"));
		             emailMsg.addRecipient(Message.RecipientType.TO,
		                              new InternetAddress(email, name));
		             emailMsg.setSubject(subj);
		             emailMsg.setText(message);

		             Transport.send(emailMsg);
		             
		             log.info("Sent group message to " + email);

		         }
		         catch (AddressException e)
		         {
		             msg = "Error: Unable to send message [" + e.getMessage() + "]";
		             log.severe(msg);
		         }
		         catch (MessagingException e)
		         {
		        	 msg = "Error: Unable to send message [" + e.getMessage() + "]";
		        	 log.severe(msg);
		         }
			 }
		}
		
		// Update a user's weight for a week
		else if(req.getParameter("email") != null && req.getParameter("week") != null && req.getParameter("weight") != null)
		{
			String email  = req.getParameter("email");
			String week   = req.getParameter("week");
			String weight = req.getParameter("weight");
			
			// Make sure this is a valid user
			Query userQ = new Query("User");
			userQ.setFilter(
					new FilterPredicate("email", FilterOperator.EQUAL, email)
			);

			PreparedQuery userPq = datastore.prepare(userQ);
			if(userPq.countEntities(FetchOptions.Builder.withDefaults()) == 0)
			{
				msg = "Error: Invalid email address";
			}
			else				
			{
				Entity user = userPq.asSingleEntity();
				String name = user.getProperty("name").toString();
				
				user.setProperty("week" + week, weight);
				log.info("Admin updated weight for " + email + " for week " + week + " to " + weight);
				
				// For weeks past week 1, set the % change
				if(!week.equals("1"))
				{
					double change = 0.0;
					double week1  = Double.parseDouble(user.getProperty("week1").toString());
					double currWeek = Double.parseDouble(weight);
					
					// If the 2 weights are the same, we need to avoid an issue where floating
					// point subtraction can cause non-zero deltas, which show up as -0.0%
					if(currWeek != week1)
					{
						if(Double.parseDouble(weight) != 0.0)
						{
							change = ((week1 - currWeek) / week1) * -100;
						}
					}
					
					user.setProperty("week" + week + "change", change);
					log.info("Admin updated weight percent change to " + change);
				}
				
				datastore.put(user);

				msg = "Changed weight for " + email + " for week " + week + " to " + weight;
				
			}
		}
		
		// Setting user to disabled or enabled
		else if(req.getParameter("email") != null && req.getParameter("status") != null)
		{
			String email  = req.getParameter("email");
			String status = req.getParameter("status");
			
			// Find this user in datastore
			Entity user = DatastoreUtil.getUserByEmail(email);
			
			if(user == null)
			{
				msg = "Failed to update user status. Unknown email " + email;
				log.warning(msg);
			}
			else
			{
				user.setProperty("status", status);
				datastore.put(user);
				msg = "Admin set status to " + status + " for user " + email;
				log.info(msg);
			}
		}
		
		// Not a valid POST command
		else
		{
			msg = "Error: Invalid data for POST";
		}

		// Send back a normal GET response, plus any error/success messages
		doGet(req, resp, msg);
	}
}